/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 */

/* global Windows */

const MediaFile = require('cordova-plugin-media-capture.MediaFile');
const CaptureError = require('cordova-plugin-media-capture.CaptureError');
const CaptureAudioOptions = require('cordova-plugin-media-capture.CaptureAudioOptions');
const CaptureVideoOptions = require('cordova-plugin-media-capture.CaptureVideoOptions');
const MediaFileData = require('cordova-plugin-media-capture.MediaFileData');

/*
 * Class that combines all logic for capturing picture and video on WP8.1
 */
function MediaCaptureProxy () {
    let previewContainer;
    let capturePreview = null;
    let captureCancelButton = null; // eslint-disable-line no-unused-vars
    let captureSettings = null;
    let captureStarted = false;
    let capturedPictureFile;
    let capturedVideoFile;
    let capture = null;

    const CaptureNS = Windows.Media.Capture;

    /**
     * Helper function that toggles visibility of DOM elements with provided ids
     * @param {String} variable number of elements' ids which visibility needs to be toggled
     */
    function toggleElements () {
        // convert arguments to array
        const args = Array.prototype.slice.call(arguments);
        args.forEach(function (buttonId) {
            const buttonEl = document.getElementById(buttonId);
            if (buttonEl) {
                const curDisplayStyle = buttonEl.style.display;
                buttonEl.style.display = curDisplayStyle === 'none' ? 'block' : 'none';
            }
        });
    }

    /**
     * Creates basic camera UI with preview 'video' element and 'Cancel' button
     * Capture starts, when you clicking on preview.
     */
    function createCameraUI () {
        const buttonStyle = 'margin: 7px; border: 2.5px solid white; width: 45%; height: 35px; color: white; background-color: black;';

        previewContainer = document.createElement('div');
        previewContainer.style.cssText =
            'background-position: 50% 50%; background-repeat: no-repeat; background-size: contain; background-color: black; left: 0px; top: 0px; width: 100%; height: 100%; position: fixed; z-index: 9999';
        previewContainer.innerHTML =
            '<video id="capturePreview" style="width: 100%; height: 100%"></video>' +
            '<div id="previewButtons" style="width: 100%; bottom: 0px; display: flex; position: absolute; justify-content: space-around; background-color: black;">' +
            '<button id="takePicture" style="' +
            buttonStyle +
            '">Capture</button>' +
            '<button id="cancelCapture" style="' +
            buttonStyle +
            '">Cancel</button>' +
            '<button id="selectPicture" style="display: none; ' +
            buttonStyle +
            '">Accept</button>' +
            '<button id="retakePicture" style="display: none; ' +
            buttonStyle +
            '">Retake</button>' +
            '</div>';

        document.body.appendChild(previewContainer);

        // Create fullscreen preview
        capturePreview = document.getElementById('capturePreview');

        // Create cancel button
        captureCancelButton = document.getElementById('cancelCapture');

        capture = new CaptureNS.MediaCapture();

        captureSettings = new CaptureNS.MediaCaptureInitializationSettings();
        captureSettings.streamingCaptureMode = CaptureNS.StreamingCaptureMode.audioAndVideo;
    }

    /**
     * Starts camera preview and binds provided callbacks to controls
     * @param  {function} takeCallback   Callback for Take button
     * @param  {function} errorCallback  Callback for Cancel button + default error callback
     * @param  {function} selectCallback Callback for Select button
     * @param  {function} retakeCallback Callback for Retake button
     */
    function startCameraPreview (takeCallback, errorCallback, selectCallback, retakeCallback) {
        // try to select appropriate device for capture
        // rear camera is preferred option
        const expectedPanel = Windows.Devices.Enumeration.Panel.back;
        Windows.Devices.Enumeration.DeviceInformation.findAllAsync(Windows.Devices.Enumeration.DeviceClass.videoCapture).done(function (
            devices
        ) {
            if (devices.length > 0) {
                devices.forEach(function (currDev) {
                    if (currDev.enclosureLocation && currDev.enclosureLocation.panel && currDev.enclosureLocation.panel === expectedPanel) {
                        captureSettings.videoDeviceId = currDev.id;
                    }
                });

                capture.initializeAsync(captureSettings).done(
                    function () {
                        // This is necessary since WP8.1 MediaCapture outputs video stream rotated 90 degrees CCW
                        // TODO: This can be not consistent across devices, need additional testing on various devices
                        // msdn.microsoft.com/en-us/library/windows/apps/hh452807.aspx
                        capture.setPreviewRotation(Windows.Media.Capture.VideoRotation.clockwise90Degrees);
                        capturePreview.msZoom = true;

                        capturePreview.src = URL.createObjectURL(capture); // eslint-disable-line no-undef
                        capturePreview.play();

                        previewContainer.style.display = 'block';

                        // Bind events to controls
                        capturePreview.onclick = takeCallback;
                        document.getElementById('takePicture').onclick = takeCallback;
                        document.getElementById('cancelCapture').onclick = function () {
                            errorCallback(CaptureError.CAPTURE_NO_MEDIA_FILES);
                        };
                        document.getElementById('selectPicture').onclick = selectCallback;
                        document.getElementById('retakePicture').onclick = retakeCallback;
                    },
                    function (err) {
                        destroyCameraPreview();
                        errorCallback(CaptureError.CAPTURE_INTERNAL_ERR, err);
                    }
                );
            } else {
                // no appropriate devices found
                destroyCameraPreview();
                errorCallback(CaptureError.CAPTURE_INTERNAL_ERR);
            }
        });
    }

    /**
     * Destroys camera preview, removes all elements created
     */
    function destroyCameraPreview () {
        capturePreview.pause();
        capturePreview.src = null;
        if (previewContainer) {
            document.body.removeChild(previewContainer);
        }
        if (capture) {
            capture.stopRecordAsync();
            capture = null;
        }
    }

    return {
        /**
         * Initiate video capture using MediaCapture class
         * @param  {function} successCallback Called, when user clicked on preview, with captured file object
         * @param  {function} errorCallback   Called on any error
         */
        captureVideo: function (successCallback, errorCallback) {
            try {
                createCameraUI();
                startCameraPreview(function () {
                    // This callback called twice: whem video capture started and when it ended
                    // so we need to check capture status
                    if (!captureStarted) {
                        // remove cancel button and rename 'Take' button to 'Stop'
                        toggleElements('cancelCapture');
                        document.getElementById('takePicture').text = 'Stop';

                        const encodingProperties = Windows.Media.MediaProperties.MediaEncodingProfile.createMp4(
                            Windows.Media.MediaProperties.VideoEncodingQuality.auto
                        );
                        const generateUniqueCollisionOption = Windows.Storage.CreationCollisionOption.generateUniqueName;
                        const localFolder = Windows.Storage.ApplicationData.current.localFolder;

                        localFolder.createFileAsync('cameraCaptureVideo.mp4', generateUniqueCollisionOption).done(
                            function (capturedFile) {
                                capture.startRecordToStorageFileAsync(encodingProperties, capturedFile).done(
                                    function () {
                                        capturedVideoFile = capturedFile;
                                        captureStarted = true;
                                    },
                                    function (err) {
                                        destroyCameraPreview();
                                        errorCallback(CaptureError.CAPTURE_INTERNAL_ERR, err);
                                    }
                                );
                            },
                            function (err) {
                                destroyCameraPreview();
                                errorCallback(CaptureError.CAPTURE_INTERNAL_ERR, err);
                            }
                        );
                    } else {
                        capture.stopRecordAsync().done(function () {
                            destroyCameraPreview();
                            successCallback(capturedVideoFile);
                        });
                    }
                }, errorCallback);
            } catch (ex) {
                destroyCameraPreview();
                errorCallback(CaptureError.CAPTURE_INTERNAL_ERR, ex);
            }
        },

        /**
         * Initiate image capture using MediaCapture class
         * @param  {function} successCallback Called, when user clicked on preview, with captured file object
         * @param  {function} errorCallback   Called on any error
         */
        capturePhoto: function (successCallback, errorCallback) {
            try {
                createCameraUI();
                startCameraPreview(
                    // Callback for Take button - captures intermediate image file.
                    function () {
                        const encodingProperties = Windows.Media.MediaProperties.ImageEncodingProperties.createJpeg();
                        const overwriteCollisionOption = Windows.Storage.CreationCollisionOption.replaceExisting;
                        const tempFolder = Windows.Storage.ApplicationData.current.temporaryFolder;

                        tempFolder.createFileAsync('cameraCaptureImage.jpg', overwriteCollisionOption).done(
                            function (capturedFile) {
                                capture.capturePhotoToStorageFileAsync(encodingProperties, capturedFile).done(
                                    function () {
                                        // store intermediate result in object's global variable
                                        capturedPictureFile = capturedFile;
                                        // show pre-captured image and toggle visibility of all buttons
                                        previewContainer.style.backgroundImage = 'url("' + 'ms-appdata:///temp/' + capturedFile.name + '")';
                                        toggleElements('capturePreview', 'takePicture', 'cancelCapture', 'selectPicture', 'retakePicture');
                                    },
                                    function (err) {
                                        destroyCameraPreview();
                                        errorCallback(CaptureError.CAPTURE_INTERNAL_ERR, err);
                                    }
                                );
                            },
                            function (err) {
                                destroyCameraPreview();
                                errorCallback(CaptureError.CAPTURE_INTERNAL_ERR, err);
                            }
                        );
                    },
                    // error + cancel callback
                    function (err) {
                        destroyCameraPreview();
                        errorCallback(err);
                    },
                    // Callback for Select button - copies intermediate file into persistent application's storage
                    function () {
                        const generateUniqueCollisionOption = Windows.Storage.CreationCollisionOption.generateUniqueName;
                        const localFolder = Windows.Storage.ApplicationData.current.localFolder;

                        capturedPictureFile.copyAsync(localFolder, capturedPictureFile.name, generateUniqueCollisionOption).done(
                            function (copiedFile) {
                                destroyCameraPreview();
                                successCallback(copiedFile);
                            },
                            function (err) {
                                destroyCameraPreview();
                                errorCallback(err);
                            }
                        );
                    },
                    // Callback for retake button - just toggles visibility of necessary elements
                    function () {
                        toggleElements('capturePreview', 'takePicture', 'cancelCapture', 'selectPicture', 'retakePicture');
                    }
                );
            } catch (ex) {
                destroyCameraPreview();
                errorCallback(CaptureError.CAPTURE_INTERNAL_ERR, ex);
            }
        }
    };
}

module.exports = {
    captureAudio: function (successCallback, errorCallback, args) {
        const options = args[0];

        const audioOptions = new CaptureAudioOptions();
        if (typeof options.duration === 'undefined') {
            audioOptions.duration = 3600; // Arbitrary amount, need to change later
        } else if (options.duration > 0) {
            audioOptions.duration = options.duration;
        } else {
            errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT));
            return;
        }

        // Some shortcuts for long namespaces
        const CaptureNS = Windows.Media.Capture;
        const MediaPropsNS = Windows.Media.MediaProperties;
        const localAppData = Windows.Storage.ApplicationData.current.localFolder;
        const generateUniqueName = Windows.Storage.NameCollisionOption.generateUniqueName;

        const mediaCapture = new CaptureNS.MediaCapture();
        const mediaCaptureSettings = new CaptureNS.MediaCaptureInitializationSettings();
        const mp3EncodingProfile = new MediaPropsNS.MediaEncodingProfile.createMp3(MediaPropsNS.AudioEncodingQuality.auto); // eslint-disable-line new-cap
        const m4aEncodingProfile = new MediaPropsNS.MediaEncodingProfile.createM4a(MediaPropsNS.AudioEncodingQuality.auto); // eslint-disable-line new-cap

        mediaCaptureSettings.streamingCaptureMode = CaptureNS.StreamingCaptureMode.audio;

        let capturedFile;
        let stopRecordTimeout;

        const stopRecord = function () {
            mediaCapture.stopRecordAsync().then(
                function () {
                    capturedFile.getBasicPropertiesAsync().then(
                        function (basicProperties) {
                            const result = new MediaFile(
                                capturedFile.name,
                                'ms-appdata:///local/' + capturedFile.name,
                                capturedFile.contentType,
                                basicProperties.dateModified,
                                basicProperties.size
                            );
                            result.fullPath = capturedFile.path;
                            successCallback([result]);
                        },
                        function () {
                            errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES));
                        }
                    );
                },
                function () {
                    errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES));
                }
            );
        };

        mediaCapture.initializeAsync(mediaCaptureSettings).done(function () {
            localAppData.createFileAsync('captureAudio.mp3', generateUniqueName).then(
                function (storageFile) {
                    capturedFile = storageFile;
                    mediaCapture.startRecordToStorageFileAsync(mp3EncodingProfile, capturedFile).then(
                        function () {
                            stopRecordTimeout = setTimeout(stopRecord, audioOptions.duration * 1000);
                        },
                        function (err) {
                            // -1072868846 is the error code for "No suitable transform was found to encode or decode the content."
                            // so we try to use another (m4a) format
                            if (err.number === -1072868846) {
                                // first we clear existing timeout to prevent success callback to be called with invalid arguments
                                // second we start same actions to try to record m4a audio
                                clearTimeout(stopRecordTimeout);
                                localAppData.createFileAsync('captureAudio.m4a', generateUniqueName).then(function (storageFile) {
                                    capturedFile = storageFile;
                                    mediaCapture.startRecordToStorageFileAsync(m4aEncodingProfile, capturedFile).then(
                                        function () {
                                            stopRecordTimeout = setTimeout(stopRecord, audioOptions.duration * 1000);
                                        },
                                        function () {
                                            // if we here, we're totally failed to record either mp3 or m4a
                                            errorCallback(new CaptureError(CaptureError.CAPTURE_INTERNAL_ERR));
                                        }
                                    );
                                });
                            } else {
                                errorCallback(new CaptureError(CaptureError.CAPTURE_INTERNAL_ERR));
                            }
                        }
                    );
                },
                function () {
                    errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES));
                }
            );
        });
    },

    captureImage: function (successCallback, errorCallback, args) {
        const CaptureNS = Windows.Media.Capture;

        function fail (code, data) {
            const err = new CaptureError(code);
            err.message = data;
            errorCallback(err);
        }

        // Check if necessary API available
        if (!CaptureNS.CameraCaptureUI) {
            // We are running on WP8.1 which lacks CameraCaptureUI class
            // so we need to use MediaCapture class instead and implement custom UI for camera

            const proxy = new MediaCaptureProxy();

            proxy.capturePhoto(
                function (photoFile) {
                    photoFile.getBasicPropertiesAsync().done(
                        function (basicProperties) {
                            const result = new MediaFile(
                                photoFile.name,
                                'ms-appdata:///local/' + photoFile.name,
                                photoFile.contentType,
                                basicProperties.dateModified,
                                basicProperties.size
                            );
                            result.fullPath = photoFile.path;
                            successCallback([result]);
                        },
                        function (err) {
                            fail(CaptureError.CAPTURE_INTERNAL_ERR, err);
                        }
                    );
                },
                function (err) {
                    fail(err);
                }
            );
        } else {
            const cameraCaptureUI = new Windows.Media.Capture.CameraCaptureUI();
            cameraCaptureUI.photoSettings.allowCropping = true;
            cameraCaptureUI.photoSettings.maxResolution = Windows.Media.Capture.CameraCaptureUIMaxPhotoResolution.highestAvailable;
            cameraCaptureUI.photoSettings.format = Windows.Media.Capture.CameraCaptureUIPhotoFormat.jpeg;
            cameraCaptureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo).done(
                function (file) {
                    if (file) {
                        file.moveAsync(
                            Windows.Storage.ApplicationData.current.localFolder,
                            'cameraCaptureImage.jpg',
                            Windows.Storage.NameCollisionOption.generateUniqueName
                        ).then(
                            function () {
                                file.getBasicPropertiesAsync().then(
                                    function (basicProperties) {
                                        const result = new MediaFile(
                                            file.name,
                                            'ms-appdata:///local/' + file.name,
                                            file.contentType,
                                            basicProperties.dateModified,
                                            basicProperties.size
                                        );
                                        result.fullPath = file.path;
                                        successCallback([result]);
                                    },
                                    function () {
                                        errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES));
                                    }
                                );
                            },
                            function () {
                                errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES));
                            }
                        );
                    } else {
                        errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES));
                    }
                },
                function () {
                    errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES));
                }
            );
        }
    },

    captureVideo: function (successCallback, errorCallback, args) {
        const options = args[0];
        const CaptureNS = Windows.Media.Capture;

        function fail (code, data) {
            const err = new CaptureError(code);
            err.message = data;
            errorCallback(err);
        }

        // Check if necessary API available
        if (!CaptureNS.CameraCaptureUI) {
            // We are running on WP8.1 which lacks CameraCaptureUI class
            // so we need to use MediaCapture class instead and implement custom UI for camera

            const proxy = new MediaCaptureProxy();

            proxy.captureVideo(function (videoFile) {
                videoFile.getBasicPropertiesAsync().done(
                    function (basicProperties) {
                        const result = new MediaFile(
                            videoFile.name,
                            'ms-appdata:///local/' + videoFile.name,
                            videoFile.contentType,
                            basicProperties.dateModified,
                            basicProperties.size
                        );
                        result.fullPath = videoFile.path;
                        successCallback([result]);
                    },
                    function (err) {
                        fail(CaptureError.CAPTURE_INTERNAL_ERR, err);
                    }
                );
            }, fail);
        } else {
            const videoOptions = new CaptureVideoOptions();
            if (options.duration && options.duration > 0) {
                videoOptions.duration = options.duration;
            }
            if (options.limit > 1) {
                videoOptions.limit = options.limit;
            }
            const cameraCaptureUI = new Windows.Media.Capture.CameraCaptureUI();
            cameraCaptureUI.videoSettings.allowTrimming = true;
            cameraCaptureUI.videoSettings.format = Windows.Media.Capture.CameraCaptureUIVideoFormat.mp4;
            cameraCaptureUI.videoSettings.maxDurationInSeconds = videoOptions.duration;
            cameraCaptureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.video).then(
                function (file) {
                    if (file) {
                        file.moveAsync(
                            Windows.Storage.ApplicationData.current.localFolder,
                            'cameraCaptureVideo.mp4',
                            Windows.Storage.NameCollisionOption.generateUniqueName
                        ).then(
                            function () {
                                file.getBasicPropertiesAsync().then(
                                    function (basicProperties) {
                                        const result = new MediaFile(
                                            file.name,
                                            'ms-appdata:///local/' + file.name,
                                            file.contentType,
                                            basicProperties.dateModified,
                                            basicProperties.size
                                        );
                                        result.fullPath = file.path;
                                        successCallback([result]);
                                    },
                                    function () {
                                        errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES));
                                    }
                                );
                            },
                            function () {
                                errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES));
                            }
                        );
                    } else {
                        errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES));
                    }
                },
                function () {
                    errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES));
                }
            );
        }
    },

    getFormatData: function (successCallback, errorCallback, args) {
        Windows.Storage.StorageFile.getFileFromPathAsync(args[0]).then(
            function (storageFile) {
                const mediaTypeFlag = String(storageFile.contentType).split('/')[0].toLowerCase();
                if (mediaTypeFlag === 'audio') {
                    storageFile.properties.getMusicPropertiesAsync().then(
                        function (audioProperties) {
                            successCallback(new MediaFileData(null, audioProperties.bitrate, 0, 0, audioProperties.duration / 1000));
                        },
                        function () {
                            errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT));
                        }
                    );
                } else if (mediaTypeFlag === 'video') {
                    storageFile.properties.getVideoPropertiesAsync().then(
                        function (videoProperties) {
                            successCallback(
                                new MediaFileData(
                                    null,
                                    videoProperties.bitrate,
                                    videoProperties.height,
                                    videoProperties.width,
                                    videoProperties.duration / 1000
                                )
                            );
                        },
                        function () {
                            errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT));
                        }
                    );
                } else if (mediaTypeFlag === 'image') {
                    storageFile.properties.getImagePropertiesAsync().then(
                        function (imageProperties) {
                            successCallback(new MediaFileData(null, 0, imageProperties.height, imageProperties.width, 0));
                        },
                        function () {
                            errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT));
                        }
                    );
                } else {
                    errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT));
                }
            },
            function () {
                errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT));
            }
        );
    }
};

require('cordova/exec/proxy').add('Capture', module.exports);
